/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-12-10     xu       the first version
 */
#include "ui_draw.h"
#include "EPD_4in2b_V2.h"
#include "EPD_4in2b_V2_test.h"
#include "GUI_Paint.h"
#include "amap_weather_api.h"
#include "chinese_date_api.h"
#include "gbk2utf8.h"
#include "hzk16.h"
#include "time_utils.h"
#include "ui_consts.h"

#define DBG_TAG "ui"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define VERTICAL_LINE_NUMBERS 7
#define VERTICAL_COL_WIDTH 50

// #define MONTH_ON_WEEKS 5

#define TITLE_ROW_HEIGHT 26
#define WEEKNAME_ROW_HEIGHT 22
#define MONTHDAY_ROW_HEIGHT 42

#define MONTHDAY_BASE_YCOORD (TITLE_ROW_HEIGHT + WEEKNAME_ROW_HEIGHT)

static uint16_t horizon_line_numbers = 7;
static uint16_t monthday_raw_base_y = 0;
static uint16_t horizon_line_ycoord[] = {
    TITLE_ROW_HEIGHT,
    MONTHDAY_BASE_YCOORD,
    MONTHDAY_BASE_YCOORD + MONTHDAY_ROW_HEIGHT,
    MONTHDAY_BASE_YCOORD + MONTHDAY_ROW_HEIGHT * 2,
    MONTHDAY_BASE_YCOORD + MONTHDAY_ROW_HEIGHT * 3,
    MONTHDAY_BASE_YCOORD + MONTHDAY_ROW_HEIGHT * 4,
    MONTHDAY_BASE_YCOORD + MONTHDAY_ROW_HEIGHT * 5,
};

#define HORIZON_SOLID_LINES 2
#define VERTICAL_SOLID_LINES 1

static rt_bool_t show_today_infos = RT_TRUE;
static rt_bool_t show_weather_infos = RT_TRUE;
static rt_bool_t show_weekday_names = RT_TRUE;
static rt_bool_t show_week_numbers = RT_TRUE;

static time_t cmd1_time = 0; // current month d1, same time as current in day
static struct tm cmd1_tm = {0};

static char buffer[128] = "";

// https://zh.cppreference.com/w/cpp/chrono/c/tm
static void print_time()
{
    time_t time_seconds = time(NULL);
    struct tm tm_struct = {0};
    rt_memcpy(&tm_struct, localtime(&time_seconds), sizeof(tm_struct));
    rt_kprintf("Date: %04d-%02d-%02d\n", tm_struct.tm_year + 1900, tm_struct.tm_mon + 1, tm_struct.tm_mday);
    rt_kprintf("Time: %02d:%02d:%02d\n", tm_struct.tm_hour, tm_struct.tm_min, tm_struct.tm_sec);
    rt_kprintf("wday: %d\n", tm_struct.tm_wday);
    rt_kprintf("asctime: %s\n", asctime(&tm_struct));
}

void draw_daily_view()
{
    print_time();
    // get current time info
    get_current_time();

    // get first day info of current month
    cmd1_time = current_time - (current_tm.tm_mday - 1) * SECONDS_PER_DAY;
    rt_memcpy(&cmd1_tm, localtime(&cmd1_time), sizeof(cmd1_tm));
    int d1_wday = cmd1_tm.tm_wday;
    int d1_yday = cmd1_tm.tm_yday;

    int month_days = get_current_month_days();
    int month_weeks = (d1_wday + month_days) / 7 + (((d1_wday + month_days) % 7) ? 1 : 0);
    LOG_D("month days=%d, weeks=%d", month_days, month_weeks);
    LOG_D("d1_wday=%d, d1_yday=%d", d1_wday, d1_yday);

    EPD_4IN2B_V2_Clear();

    LOG_D("Draw BLACK/WHITE pixels");
    Paint_NewImage(IMAGE_BW, EPD_4IN2B_V2_WIDTH, EPD_4IN2B_V2_HEIGHT, IMAGE_ROTATE_0, IMAGE_COLOR_POSITIVE);
    Paint_Clear(WHITE);

    for (int i = 0; i < wday_name_nums; i++) {
        const char* name = wday_name_cn[i];
        const short name_len = strlen(name);
        LOG_D("wday_name_cn[%d]: %s %d", i, name, strlen(name));
        for (int j = 0; j < name_len; j += 2) {
            hzk16_code_t code = {.gbk_code = {name[j], name[j + 1]}};
            rt_kprintf("code = 0x%02X 0x%02X\n", code.gbk_code[0], code.gbk_code[1]);

            hzk16_data_t data = hzk16_data_create();
            if (!data) {
                rt_kprintf("create hzk16 data failed!\n");
                continue;
            }

            hzk16_get_font_data(code, data);
            // print_data(data->data, data->size);

            uint16_t font_size = hzk16_font_size;
            Paint_DrawRegion(j / 2 * font_size, i * font_size, font_size, font_size, data->data, data->size, font_size,
                             0);
            hzk16_data_destroy(data);
        }
    }

    LOG_D("Draw RED pixels");
    Paint_NewImage(IMAGE_BWR, EPD_4IN2B_V2_WIDTH, EPD_4IN2B_V2_HEIGHT, IMAGE_ROTATE_0, IMAGE_COLOR_POSITIVE);
    Paint_Clear(WHITE);

    EPD_4IN2B_V2_Display();
}

struct chinese_number_mixture {
    enum { CN, NU, EN } type;
    union {
        const char* cn;
        const char* en;
        struct {
            const char* format;
            int value;
        } nu;
    } data;
};

static void draw_weather_infos()
{
    const sFONT* font = &Font16;
    hzk16_code_t code = {0};

    amap_weather_info_t wea_info = amap_weather_get(LIVE_WEATHER);
    if (!wea_info) {
        LOG_D("get weather info failed!");
        return;
    }

    struct weather_live_data* live_wea = &wea_info->data.live_data;
    struct chinese_number_mixture char_array[] = {
        {CN, .data.cn = live_wea->city},      {EN, .data.en = " "},
        {CN, .data.cn = live_wea->weather},   {EN, .data.en = " "},
        {CN, .data.cn = weather_out_temp_cn}, {EN, .data.en = live_wea->temperature},
        {CN, .data.cn = weather_du},
    };
    LOG_D("live_wea: %s, %s, %s", live_wea->city, live_wea->weather, live_wea->temperature);

    hzk16_data_t data = hzk16_data_create();
    RT_ASSERT(data);

    uint16_t px = EPD_4IN2B_V2_WIDTH - hzk16_font_size;
    const uint16_t py = TITLE_ROW_HEIGHT / 2 - font->Height / 2;
    for (int i = ARRAY_SIZE(char_array) - 1; i >= 0; i--) {
        if (char_array[i].type == NU) {
            snprintf(buffer, sizeof(buffer), char_array[i].data.nu.format, char_array[i].data.nu.value);
            px -= strlen(buffer) * font->Width;
            Paint_DrawString_EN(px, py + 2, buffer, (sFONT*)font, WHITE, BLACK);
        } else if (char_array[i].type == EN) {
            px -= strlen(char_array[i].data.en) * font->Width;
            Paint_DrawString_EN(px, py + 2, char_array[i].data.en, (sFONT*)font, WHITE, BLACK);
        } else {
            int len = strlen(char_array[i].data.cn);
            px -= len / 2 * hzk16_font_size;
            for (int p = 0; p < len; p += 2) {
                rt_memcpy(&code, &char_array[i].data.cn[p], 2);
                hzk16_get_font_data(code, data);
                Paint_DrawRegion(px + (p / 2) * hzk16_font_size, py, hzk16_font_size, hzk16_font_size, data->data,
                                 data->size, hzk16_font_size, 0);
            }
        }
    }
    hzk16_data_destroy(data);
    amap_weather_delete(wea_info);
}

static void draw_today_infos()
{
    const sFONT* font = &Font16;
    hzk16_code_t code = {0};
    hzk16_data_t data = hzk16_data_create();
    uint16_t px = font->Width / 2;
    const uint16_t py = TITLE_ROW_HEIGHT / 2 - font->Height / 2;
    RT_ASSERT(data);

    struct chinese_number_mixture char_array[] = {
        {NU, .data.nu = {"%04d", current_tm.tm_year + 1900}}, {CN, .data.cn = chinese_nian},
        {NU, .data.nu = {"%02d", current_tm.tm_mon + 1}},     {CN, .data.cn = chinese_yue},
        {NU, .data.nu = {"%02d", current_tm.tm_mday}},        {CN, .data.cn = chinese_ri},
    };

    for (rt_size_t i = 0; i < ARRAY_SIZE(char_array); i++) {
        if (char_array[i].type == NU) {
            snprintf(buffer, sizeof(buffer), char_array[i].data.nu.format, char_array[i].data.nu.value);
            Paint_DrawString_EN(px, py + 2, buffer, (sFONT*)font, WHITE, BLACK);
            px += strlen(buffer) * font->Width;
        } else {
            rt_memcpy(&code, char_array[i].data.cn, 2);
            hzk16_get_font_data(code, data);
            Paint_DrawRegion(px, py, hzk16_font_size, hzk16_font_size, data->data, data->size, hzk16_font_size, 0);
            px += hzk16_font_size;
        }
    }
    hzk16_data_destroy(data);
}

static void draw_weekday_names()
{
    for (int i = 0; i < wday_name_nums; i++) {
        sFONT* font = &Font12;
        snprintf(buffer, sizeof(buffer), "%s", wday_name_en[i]);
        int len = strlen(buffer);
        uint16_t px = (VERTICAL_COL_WIDTH / 2) + i * VERTICAL_COL_WIDTH - (len * font->Width) / 2;
        uint16_t py = horizon_line_ycoord[0] + (font->Height / 2);
        Paint_DrawString_EN(px, py, buffer, font, WHITE, BLACK);
    }
}

static void draw_week_numbers(int d1_yday, int month_weeks)
{
    time_t y1_time = current_time - current_tm.tm_yday * SECONDS_PER_DAY;
    struct tm y1_tm = {0};
    rt_memcpy(&y1_tm, localtime(&y1_time), sizeof(y1_tm));
    uint16_t d1_wno = (y1_tm.tm_wday + d1_yday) / DAYS_PER_WEEK + 1;
    for (int i = 0; i < month_weeks; i++) {
        int wno = d1_wno + i;
        sFONT* font = &Font12;
        snprintf(buffer, sizeof(buffer), "%d", wno);
        int ds = wno >= 10 ? 2 : 1; // number of digits
        uint16_t px = (VERTICAL_COL_WIDTH / 2) - (ds * font->Width) / 2;
        uint16_t py = monthday_raw_base_y + i * MONTHDAY_ROW_HEIGHT + (MONTHDAY_ROW_HEIGHT / 2) - (font->Height / 2);
        Paint_DrawString_EN(px, py, buffer, font, WHITE, BLACK);
    }
}

static void draw_monthday_number_name(int mday, int d1_wday, uint8_t bg_color, uint8_t fg_color)
{
    sFONT* font = &Font16;
    snprintf(buffer, sizeof(buffer), "%d", mday);
    int ds = mday >= 10 ? 2 : 1; // number of digits
    uint16_t cx =
        ((d1_wday + mday - 1) % DAYS_PER_WEEK) * VERTICAL_COL_WIDTH + VERTICAL_COL_WIDTH + (VERTICAL_COL_WIDTH / 2);
    uint16_t cy =
        ((d1_wday + mday - 1) / DAYS_PER_WEEK) * MONTHDAY_ROW_HEIGHT + monthday_raw_base_y + (MONTHDAY_ROW_HEIGHT / 2);
    uint16_t px = cx - (ds * font->Width) / 2;
    uint16_t py = cy - font->Height;
    uint8_t inv_color = 0;
    if (mday == current_tm.tm_mday) {
        uint8_t temp = bg_color;
        bg_color = fg_color;
        fg_color = temp;
        inv_color = 1;
        // hightlight today with a inversed color rectangle
        Paint_DrawRectangle(cx - VERTICAL_COL_WIDTH / 2, cy - MONTHDAY_ROW_HEIGHT / 2, cx + VERTICAL_COL_WIDTH / 2,
                            cy + MONTHDAY_ROW_HEIGHT / 2, bg_color, DRAW_FILL_FULL, DOT_PIXEL_1X1);
    }
    Paint_DrawString_EN(px, py, buffer, font, bg_color, fg_color);

    // draw chinese mday names
    const char* mday_cn = mday_name_cn[mday - 1];
    const short mday_cn_bytes = strlen(mday_cn);
    const short mday_cn_width = (mday_cn_bytes / 2) * hzk16_font_size;

    time_t time_iter = cmd1_time + (mday - 1) * SECONDS_PER_DAY;
    struct tm tm_iter = {0};
    rt_memcpy(&tm_iter, localtime(&time_iter), sizeof(tm_iter));

    chinese_date_t cn_date = chinese_date_kvdb_get(&tm_iter);
    if (cn_date == RT_NULL) {
        LOG_E("get chinse date for %s failed!", tm_to_date_str(&tm_iter));
        return;
    }

    LOG_D("lunardate: %s", cn_date->lunardate);
    char* end_ptr = RT_NULL;
    long lunar_year = strtol(cn_date->lunardate, &end_ptr, 10);
    end_ptr++;
    long lunar_mon = strtol(end_ptr, &end_ptr, 10);
    end_ptr++;
    long lunar_mday = strtol(end_ptr, &end_ptr, 10);

    LOG_D("lunarparsed: %d-%d-%d", lunar_year, lunar_mon, lunar_mday);
    chinese_date_delete(cn_date);

    for (int i = 0; i < mday_cn_bytes; i += 2) {
        const char* cptr =
            &mday_name_cn[(lunar_mday - 1) % mday_name_nums][i]; // point to current drawing chinese character
        if (lunar_mday == 1) {
            cptr = &month_name_cn[(lunar_mon - 1) % month_name_nums][i];
        }
        hzk16_code_t code = {.gbk_code = {cptr[0], cptr[1]}};
        hzk16_data_t data = hzk16_data_create();
        uint16_t font_size = hzk16_font_size;
        uint16_t qx = cx - mday_cn_width / 2 + (i / 2) * font_size;
        uint16_t qy = cy;

        RT_ASSERT(data);
        hzk16_get_font_data(code, data);

        LOG_D("Draw chinse %s for mday %d", (char*)&code, lunar_mday);
        Paint_DrawRegion(qx, qy, font_size, font_size, data->data, data->size, font_size, inv_color);
        hzk16_data_destroy(data);
    }
}

void draw_month_view()
{
    print_time();
    EPD_4IN2B_V2_Clear();

    // get current time info
    current_time = time(NULL);
    rt_memcpy(&current_tm, localtime(&current_time), sizeof(current_tm));

    // get first day info of current month
    cmd1_time = current_time - (current_tm.tm_mday - 1) * SECONDS_PER_DAY;
    rt_memcpy(&cmd1_tm, localtime(&cmd1_time), sizeof(cmd1_tm));
    int d1_wday = cmd1_tm.tm_wday;
    int d1_yday = cmd1_tm.tm_yday;

    // calculate current month days and weeks
    int month_days = get_current_month_days();
    int month_weeks = (d1_wday + month_days) / 7 + (((d1_wday + month_days) % 7) ? 1 : 0);
    LOG_D("month days=%d, weeks=%d", month_days, month_weeks);
    LOG_D("d1_wday=%d, d1_yday=%d", d1_wday, d1_yday);

    // calculate horizon line postions
    horizon_line_numbers = month_weeks + 1;
    monthday_raw_base_y = EPD_4IN2B_V2_HEIGHT - month_weeks * MONTHDAY_ROW_HEIGHT;
    horizon_line_ycoord[0] = monthday_raw_base_y - WEEKNAME_ROW_HEIGHT;
    LOG_D("horizon_line_ycoord[0]: %d", horizon_line_ycoord[0]);
    for (int i = 0; i < month_weeks; i++) {
        horizon_line_ycoord[i + 1] = monthday_raw_base_y + i * MONTHDAY_ROW_HEIGHT;
        LOG_D("horizon_line_ycoord[%d]: %d", i + 1, horizon_line_ycoord[i + 1]);
    }

    LOG_D("Draw BLACK/WHITE pixels");
    Paint_NewImage(IMAGE_BW, EPD_4IN2B_V2_WIDTH, EPD_4IN2B_V2_HEIGHT, IMAGE_ROTATE_0, IMAGE_COLOR_POSITIVE);
    Paint_Clear(WHITE);

    // draw horizontal lines
    for (int i = 0; i < horizon_line_numbers; i++) {
        uint16_t sx = 0;
        uint16_t sy = horizon_line_ycoord[i];
        uint16_t ex = EPD_4IN2B_V2_WIDTH;
        uint16_t ey = sy;
        LINE_STYLE style = (i < HORIZON_SOLID_LINES) ? LINE_STYLE_SOLID : LINE_STYLE_DOTTED;
        Paint_DrawLine(sx, sy, ex, ey, BLACK, style, DOT_PIXEL_1X1);
    }

    // draw today infos
    if (show_today_infos) {
        draw_today_infos();
    }

    // draw weather infos
    if (show_weather_infos) {
        draw_weather_infos();
    }

    // draw weekday names
    if (show_weekday_names) {
        draw_weekday_names();
    }

    // draw week numbers
    if (show_week_numbers) {
        draw_week_numbers(d1_yday, month_weeks);
    }

    // draw weekdays of monthdays
    for (int i = 0; i < month_days; i++) {
        int wday = (d1_wday + i) % DAYS_PER_WEEK;
        if (wday != 0 && wday != 6) {
            int mday = i + 1;
            draw_monthday_number_name(mday, d1_wday, WHITE, BLACK);
        }
    }

    LOG_D("Draw RED pixels");
    Paint_NewImage(IMAGE_BWR, EPD_4IN2B_V2_WIDTH, EPD_4IN2B_V2_HEIGHT, IMAGE_ROTATE_0, IMAGE_COLOR_POSITIVE);
    Paint_Clear(WHITE);

    // draw vertical lines
    for (int i = 0; i < VERTICAL_LINE_NUMBERS; i++) {
        uint16_t sx = (i + 1) * VERTICAL_COL_WIDTH;
        uint16_t sy = monthday_raw_base_y;
        uint16_t ex = sx;
        uint16_t ey = EPD_4IN2B_V2_HEIGHT;
        LINE_STYLE style = LINE_STYLE_DOTTED;
        if (i == 0) {
            style = LINE_STYLE_SOLID;
            sy -= WEEKNAME_ROW_HEIGHT;
        }
        Paint_DrawLine(sx, sy, ex, ey, RED, style, DOT_PIXEL_1X1);
    }

    // draw weekends of monthdays
    for (int i = 0; i < month_days; i++) {
        int wday = (d1_wday + i) % DAYS_PER_WEEK;
        if (wday == 0 || wday == 6) {
            int mday = i + 1;
            draw_monthday_number_name(mday, d1_wday, WHITE, RED);
        }
    }
    EPD_4IN2B_V2_Display();
}